home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-05-04 | 59.7 KB | 1,826 lines | [TEXT/MPCC] |
- /* See the file Distribution for distribution terms.
- (c) Copyright 1994 Ari Halberstadt */
-
- /* Thread Library implements nonpreemptive multiple thread execution within
- a single application.
-
- 950403 aih - added custom scheduler function
- 950328 aih - removed user-settable fpu save flag
- 950313 aih - ported to native ppc
- 950220 aih - added stack size accessor function
- - added memory types for memory allocation callbacks
- 950219 aih - added sleep interval accessor function
- 950214 aih - general code review
- - added missing check for thread->enabled in calculation
- of yield interval
- 950203 aih - removed stack sniffer code
- - added memory allocation callbacks
- - added register a6 access (this may go away again in the future)
- 941207 aih - removed almost all compiler dependencies
- - added ifdef's to the few (3) places where powerpc-specific
- code is needed--a native PPC version is around the corner!
- 941020 aih - added an enabled flag
- 941012 aih - added an exit function; the exit function is called
- after the suspend function and before the thread is
- disposed of
- 941007 aih - fixed passing wrong data parameter to suspend function.
- 940706 aih - moved context switching for exceptions and the profiler,
- which are used in winter shell, out of thread library
- and into a separate file that will be distributed with
- winter shell.
- 940629 aih - added support for universal headers
- 940617 aih - fixed error in detecting events that was introduced on 940609
- - added support for context-switching a profiler
- - improved context switches for exceptions (made faster)
- - removed status field and operations
- - added ThreadErrorType and defined some error codes
- - fixed order of execution of actions taken when a thread
- is resumed (the stack sniffer was being resumed before
- the low-memory globals were restored)
- 940609 aih - to prevent context switches from threads other than the
- main thread, I removed the call to EventAvail, and replaced
- it with checks of the CurActive/CurDeactive low-memory
- globals and a call to CheckUpdate.
- 940531 aih - changed ExceptionType to ExceptionStructure; this change
- was made for syntactic compatability with the current
- version of Winter Shell, and has no impact on the
- functionality of Thread Library.
- 940316 aih - changed ThreadType to ThreadStructure, and ThreadSNType
- to ThreadType.
- 940311 aih - made gThread and gStackSniffer static variables
- 940309 aih - in v1.0d3 i wrote the stack sniffer sentinel value right over
- the heap zone information for the application heap. this of
- course resulted in a damaged heap. what a stupid mistake.
- now the main thread doesn't use a stack sniffer sentinel
- value, but all other threads still use the stack sniffer
- sentinel value.
- 9403?? aih - release 1.0d3
- 940228 aih - fixed error in setting thread error code in ThreadFromSN
- - added sentinel value to stack sniffer VBL task
- 940225 aih - will compile with either "LoMem.h" or "SysEqu.h"
- 940223 aih - fixed an error in the private routine ThreadStackFrame
- 940219 aih - added ThreadSleepSet, ThreadData, ThreadDataSet
- - added doubly-linked list circular queue of threads
- - all external functions refer to threads using thread serial
- numbers instead of thread pointers
- - when a thread is activated it is moved to the end of the
- queue of threads, so that the round-robbin scheduling is
- fairer (since the main thread has the highest priority).
- 940218 aih - release 1.0d2.1
- - to reduce the possibility of conflict with user-defined types,
- uses ThreadTicksType instead of TicksType, THREAD_TICKS_MAX
- instead of TICKS_MAX, and THREAD_TICKS_SEC instead of
- TICKS_SEC
- - includes actual headers instead of using THINK C's
- non-standard MacHeaders. this was done primarily so I
- could use "SysEqu.h" instead of "LoMem.h", but it will
- also make porting to another compiler easier.
- - added THREAD_DEBUG so thread debug code can be selectively
- disabled without defining NDEBUG and surrounded debug
- functions with conditional compile directives to reduce
- dead-code size in non-debug version
- - uses the OS routines Enqueue and Dequeue instead of my
- own linked-list code. this made the code cleaner and will
- make the object code even more compact.
- - made defines with prefix "LM" for all low-memory globals
- 940217 aih - put back THREAD_SET_GLOBALS code in attempt to fix
- update problems
- 940217 aih - added string to assertion debug statement, and added ifdef
- around assertfailed function to keep it from being compiled
- into non-debug version
- 940217 aih - release 1.0d2
- - for efficiency, defined TickCount as Ticks low-memory global
- - for greater ease in adding ThreadLib to other people's
- applications, removed use of exceptions. this also increases
- the efficiency of context switches in applications that
- don't use exceptions
- 940215 aih - removed THREAD_SET_GLOBALS code since it didn't seem to be
- needed
- 940214 aih - added thread serial numbers
- - added precondition for ThreadEnd
- - fixed problem with defer and combine stack adjusts
- 940213 aih - added stack sniffer VBL task
- - added functions for determining the minimum and the default
- stack sizes for threads and removed THREAD_STACK_SIZE
- 940212 aih - made threads use pointers instead of handles. using pointers
- increases the efficiency of Thread Library. this won't
- significantly increase memory fragmentation since stacks
- for threads are already allocated as pointers. (while no
- stack is allocated for the main thread, the main thread
- is created very early in the application and usually
- remains in existence until the application exits.)
- - limited release 1.0d1.1
- 940211 aih - added thread status field
- 940210 aih - release 1.0d1; got threads to work without crashing! yay! :-)
- 940204 aih - created */
-
- /*----------------------------------------------------------------------------*/
- /* include statements */
- /*----------------------------------------------------------------------------*/
-
- #include <Errors.h>
- #include <Events.h>
- #include <Gestalt.h>
- #include <LowMem.h>
- #include <Memory.h>
- #include <OSUtils.h>
- #include <Windows.h>
- #include "ThreadLibrary.h"
-
- /*----------------------------------------------------------------------------*/
- /* compiler-specific code */
- /*----------------------------------------------------------------------------*/
-
- #if ! defined(THINK_C) && ! defined(__MWERKS__) && ! defined(MPW)
-
- /* For compilers other than THINK C, you should be careful when turning
- on the optimizer. For instance, I found that the "defer and combine
- stack adjusts" option was incompatible with Thread Library. I
- recommend first getting Thread Library to work with all
- optimizations disabled. Once you know that it runs under the new
- compiler, you can enable the optimizations. If it then crashes,
- you will know that one or more of the optimizations performed by
- the compiler are incompatible with Thread Library and should
- therefore be disabled. */
-
- #error tested with THINK C 7.0, MetroWerks CW4, MPW 3.3.1
-
- #endif
-
- #ifdef THINK_C
-
- /* Thread Library will not work correctly (the heap will become
- corrupted) when the THINK C "defer and combine stack adjusts"
- optimization option is enabled. We therefore disable the option
- for this file. All other THINK C optimizations work fine with
- Thread Library. */
- #pragma options(! defer_adjust)
-
- /* Thread Library will not work if profiling is enabled. During
- context switches, the profiler's state is inconsistent, since the
- profiler's internal state may refer to the prior executing thread
- while the new thread is being switched in. This could result in a
- nasty crash. */
- #pragma options(! profile)
-
- #endif /* THINK_C */
-
- #ifdef __MWERKS__
-
- /* See note for THINK C profiler. */
- #pragma profiler off
-
- #endif /* __MWERKS__ */
-
- /*----------------------------------------------------------------------------*/
- /* debug declarations */
- /*----------------------------------------------------------------------------*/
-
- /* Define THREAD_DEBUG as 1 to enable debug code, or 0 to disable debug code.
- You can also define NDEBUG to disable debug code. Debug code is enabled
- by default if both THREAD_DEBUG and NDEBUG are undefined. */
- #ifndef THREAD_DEBUG
- #ifndef NDEBUG
- #define THREAD_DEBUG (1)
- #else
- #define THREAD_DEBUG (0)
- #endif
- #endif /* THREAD_DEBUG */
-
- #if THREAD_DEBUG
-
- #define ThreadAssert(x) ((void) ((x) || ThreadAssertFailed(#x)))
-
- static int ThreadAssertFailed(const char *msg)
- {
- const char *prompt = "ThreadAssertFailed:";
- Str255 pmsg;
- short i, j;
-
- for (i = 0; i < 255 && prompt[i]; i++)
- pmsg[i+1] = prompt[i];
- for (j = 0; i+j < 255 && msg[j]; j++)
- pmsg[i+j+1] = msg[j];
- pmsg[0] = i+j;
- DebugStr(pmsg);
- return(0);
- }
-
- #else /* THREAD_DEBUG */
-
- #define ThreadAssert(x) ((void) 0)
-
- #endif /* THREAD_DEBUG */
-
- #define require(x) ThreadAssert(x)
- #define check(x) ThreadAssert(x)
- #define ensure(x) ThreadAssert(x)
-
- /*----------------------------------------------------------------------------*/
- /* low-memory globals */
- /*----------------------------------------------------------------------------*/
-
- /* Two low-memory globals are not defined in "LowMem.h".
-
- The low-memory global variable HiHeapMark must be set when threads
- are switched so that QuickDraw will operate correctly. This may
- no longer be necessary with the Modern Memory Manager, or with
- the PowerPC version of QuickDraw. At least, that's what I'm hoping
- is the case. Otherwise, we may have to "break" the rules a bit
- to munge HiHeapMark into what we need.
-
- The low-memory global variable StkLowPt must be cleared when any
- thread other than the main thread is switched in. This is necessary
- to disable the system's stack sniffer VBL task, which otherwise
- would cause system error #28. */
-
- #define HiHeapMark (0x0BAE)
- #define StkLowPt (0x0110)
- #define LMGetHiHeapMark() (*(Ptr *) HiHeapMark)
- #define LMSetHiHeapMark(p) ((void) (*(Ptr *) HiHeapMark = (p)))
- #define LMGetStkLowPt(p) (*(Ptr *) StkLowPt)
- #define LMSetStkLowPt(p) ((void) (*(Ptr *) StkLowPt = (p)))
-
- /*----------------------------------------------------------------------------*/
- /* register access */
- /*----------------------------------------------------------------------------*/
-
- #ifdef powerc
-
- #include "regppc.h"
-
- /* indexes to special registers */
- #define REGISTER_FP (1)
- #define REGISTER_SP (3)
-
- typedef struct { ThreadRegistersPPCType gp; } ThreadRegistersType;
-
- /* save the registers */
- #define ThreadRegistersFPSave(registers) ((void) 0)
- #define ThreadRegistersGPSave(registers) ThreadRegistersPPCSave(registers)
-
- /* restore the registers */
- #define ThreadRegistersFPRestore(registers) ((void) 0)
- #define ThreadRegistersGPRestore(registers) ThreadRegistersPPCRestore((registers), 1)
-
- #else /* powerc */
-
- /* indexes to special registers */
- #define REGISTER_FP (10)
- #define REGISTER_SP (11)
-
- /* arrays of registers */
- typedef long ThreadRegistersGPType[12];
- typedef long ThreadRegistersFPType[12];
-
- /* all of the registers saved by threads */
- typedef struct {
- ThreadRegistersGPType gp; /* general purpose registers */
- ThreadRegistersFPType fp; /* floating point registers */
- } ThreadRegistersType;
-
- /* save the general purpose registers (similar to setjmp) */
- #pragma parameter __D0 ThreadRegistersGPSave(__A0)
- static long ThreadRegistersGPSave(ThreadRegistersGPType registers) =
- {
- /*
- asm {
- lea @1, a1
- moveq #0, d0
- movem.l d3-d7/a1-a7, (a0)
- @1
- }
- */
- 0x43FA, 0x0008,
- 0x7000,
- 0x48D0, 0xFEF8
- };
-
- /* restore the general purpose registers and resume execution of the
- saved thread (similar to longjmp) */
- #pragma parameter ThreadRegistersGPRestore(__A0)
- static void ThreadRegistersGPRestore(ThreadRegistersGPType registers) =
- {
- /*
- asm {
- moveq #1, d0
- movem.l (a0), d3-d7/a1-a7
- jmp (a1)
- }
- */
- 0x7001,
- 0x4CD0, 0xFEF8,
- 0x4ED1
- };
-
- /* save the floating point registers */
- #pragma parameter ThreadRegistersFPSave(__A0)
- static void ThreadRegistersFPSave(ThreadRegistersFPType registers) =
- {
- /*
- asm {
- fmovem.l fp4-fp7, (a0)
- }
- */
- 0xF210, 0xF00F
- };
-
- /* restore the floating point registers */
- #pragma parameter ThreadRegistersFPRestore(__A0)
- static void ThreadRegistersFPRestore(ThreadRegistersFPType registers) =
- {
- /* asm { fmovem.l (a0), fp4-fp7 } */
- 0xF210, 0xD00F
- };
-
- #endif /* powerc */
-
- /* return the value of the stack pointer */
- static ThreadStackPtr ThreadRegisterStackPointer(void)
- {
- ThreadRegistersType registers;
-
- (void) ThreadRegistersGPSave(registers.gp);
- return((ThreadStackPtr) registers.gp[REGISTER_SP]);
- }
-
- /* return the value of the stack frame pointer or link register */
- static ThreadStackPtr ThreadRegisterStackFramePointer(void)
- {
- ThreadRegistersType registers;
-
- (void) ThreadRegistersGPSave(registers.gp);
- return((ThreadStackPtr) registers.gp[REGISTER_FP]);
- }
-
- /*----------------------------------------------------------------------------*/
- /* type definitions, global variables, etc. */
- /*----------------------------------------------------------------------------*/
-
- /* structure describing a thread */
- typedef struct ThreadStructure {
-
- /* queue links */
- struct ThreadStructure *next; /* next thread in queue */
- struct ThreadStructure *prev; /* previous thread in queue */
-
- /* internal state */
- ThreadRegistersType registers; /* saved registers */
- ThreadErrorType error; /* error code from last function called */
- ThreadTicksType wake; /* when to wake thread */
- ThreadType sn; /* thread's serial number */
- Boolean enabled; /* true if thread is enabled */
- Boolean fpu; /* true if saves floating point state */
- Boolean main; /* true if the main thread */
-
- /* thread's stack */
- struct {
- ThreadStackPtr top; /* highest address in thread's stack */
- ThreadStackPtr limit; /* lowest address in thread's stack */
- ThreadSizeType size; /* size of thread's stack */
- } stack;
-
- /* low-memory globals */
- struct {
- Ptr heapEnd; /* value of HeapEnd low-memory global */
- Ptr applLimit; /* value of ApplLimit low-memory global */
- Ptr hiHeapMark; /* value of HiHeapMark low-memory global */
- } lm;
-
- /* application defined functions and data */
- struct {
- ThreadProcBeginEndType begin; /* called when thread is started */
- ThreadProcBeginEndType end; /* called when thread is terminated */
- ThreadProcType resume; /* called when thread is resumed */
- ThreadProcType suspend; /* called when thread is suspended */
- ThreadProcType entry; /* thread's entry point */
- void *data; /* data to pass to thread callbacks */
- } appl;
-
- } ThreadStructure, *ThreadPtr;
-
- /* queue of threads */
- typedef struct {
- ThreadPtr head; /* head of queue */
- ThreadPtr tail; /* tail of queue */
- long nelem; /* number of elements in queue */
- } ThreadQueueStructure, *ThreadQueuePtr;
-
- /* structure describing state of thread library */
- typedef struct {
- ThreadQueueStructure queue; /* queue of threads */
- ThreadErrorType error; /* error code from last function called */
- ThreadType lastsn; /* serial number of last thread created */
- ThreadPtr main; /* main thread */
- ThreadPtr active; /* currently active thread */
- ThreadPtr zombie; /* thread to dispose of */
- ThreadProcScheduleType schedule; /* scheduler callback */
- ThreadProcAllocateType allocate; /* memory allocation callback */
- ThreadProcDisposeType dispose; /* memory disposal callback */
- } ThreadStateStructure;
-
- /* state of thread library */
- static ThreadStateStructure gThread;
-
- /* a few forward-declarations of functions */
- static ThreadPtr ThreadFromSN(register ThreadType tsn);
- static void ThreadDispose(ThreadPtr thread);
-
- /*----------------------------------------------------------------------------*/
- /* more register access code */
- /*----------------------------------------------------------------------------*/
-
- /* return thread's stack pointer */
- static ThreadStackPtr ThreadStackPointer(ThreadPtr thread)
- {
- ThreadStackPtr sp = NULL;
-
- if (thread == gThread.active)
- sp = ThreadRegisterStackPointer();
- else
- sp = (ThreadStackPtr) thread->registers.gp[REGISTER_SP];
- return(sp);
- }
-
- /* return thread's register stack frame pointer */
- ThreadStackPtr ThreadStackFramePointer(ThreadType tsn)
- {
- ThreadStackPtr fp = NULL;
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread == gThread.active)
- fp = ThreadRegisterStackFramePointer();
- else
- fp = (ThreadStackPtr) thread->registers.gp[REGISTER_FP];
- return(fp);
- }
-
- /*----------------------------------------------------------------------------*/
- /* memory allocation */
- /*----------------------------------------------------------------------------*/
-
- /* This header is inserted at the start of every block of memory allocated
- by Thread Library. Among other information, the header also contains a
- pointer to the disposal function to use when disposing of the block of
- memory. Storing a pointer to the disposal function with each block allows
- the application to change the memory allocator or disposal functions after
- the block is allocated, thus eliminating a possible source of errors. */
- typedef struct {
- #if THREAD_DEBUG
- ThreadTypeType type; /* type of block */
- ThreadSizeType size; /* size of block */
- #endif
- ThreadProcDisposeType dispose;/* function to use to dispose of block */
- } ThreadMemoryHeaderType;
-
- #if THREAD_DEBUG
-
- /* These functions are used while debugging to help validate memory
- and to ensure that discarded memory is unuseable. */
-
- /* Return the size of a pointer. */
- static ThreadSizeType PtrSize(void *p)
- {
- return(((ThreadMemoryHeaderType *) p)[-1].size);
- }
-
- /* Return the type of a pointer. */
- static ThreadSizeType PtrType(void *p)
- {
- return(((ThreadMemoryHeaderType *) p)[-1].type);
- }
-
- /* Fill a pointer with garbage. */
- static void *PtrJunk(void *p)
- {
- char *q;
- ThreadSizeType i;
- ThreadSizeType n;
-
- q = p;
- n = PtrSize(p);
- for (i = 0; i < n; i++)
- q[i] = ~0;
- return(p);
- }
-
- #else /* THREAD_DEBUG */
-
- #define PtrJunk(p) ((void) 0)
-
- #endif /* THREAD_DEBUG */
-
- /* Returns the amount of memory needed to store a block of the specified
- size. This includes the overhead of the block header. */
- static ThreadSizeType PtrSizeNew(ThreadSizeType size, ThreadTypeType type)
- {
- return(size + sizeof(ThreadMemoryHeaderType));
- }
-
- /* Allocate n contiguous bytes using an application supplied allocator,
- or the default allocator (NewPtr) if no allocator was supplied by
- the application. The disposal function installed at the time the
- memory is allocated is saved with the block, so that it may be
- called to dispose of the block even the application subsequently
- changes the memory allocator. */
- static void *PtrBegin(ThreadSizeType size, ThreadTypeType type)
- {
- ThreadMemoryHeaderType *hdr;
-
- hdr = NULL;
- if (gThread.allocate) {
- hdr = gThread.allocate(PtrSizeNew(size, type), type);
- if (hdr)
- hdr->dispose = gThread.dispose;
- }
- if (! hdr && ! ThreadError()) {
- hdr = (ThreadMemoryHeaderType *) NewPtr(PtrSizeNew(size, type));
- if (hdr)
- hdr->dispose = NULL;
- else
- ThreadErrorSet(MemError() ? MemError() : memFullErr);
- }
- #if THREAD_DEBUG
- if (hdr) {
- hdr->type = type;
- hdr->size = size;
- }
- #endif
- if (! hdr && ! ThreadError())
- ThreadErrorSet(memFullErr);
- return(hdr ? hdr + 1 : NULL);
- }
-
- /* allocate and clear memory */
- static void *PtrBeginClear(ThreadSizeType size, ThreadTypeType type)
- {
- ThreadSizeType i;
- char *p;
-
- p = PtrBegin(size, type);
- if (p) {
- for (i = 0; i < size; i++)
- p[i] = 0;
- }
- return(p);
- }
-
- /* dispose of the memory */
- static void PtrEnd(void *p, ThreadSizeType size, ThreadTypeType type)
- {
- ThreadMemoryHeaderType *hdr;
-
- if (p) {
- PtrJunk(p);
- hdr = p;
- hdr--;
- #if THREAD_DEBUG
- check(hdr->size == size && hdr->type == type);
- hdr->type = THREAD_TYPE_FREE;
- hdr->size = -1;
- #endif
- if (hdr->dispose)
- hdr->dispose(hdr, PtrSizeNew(size, type), type);
- else
- DisposePtr((Ptr) hdr);
- }
- }
-
- /*----------------------------------------------------------------------------*/
- /* Everything up to this point was just definitions and utility functions.
- This is where the real meat of the code begins. The code from here on
- is much cleaner and contains few ugly preprocessor directives. */
- /*----------------------------------------------------------------------------*/
-
- /*----------------------------------------------------------------------------*/
- /* Thread Validation */
- /*----------------------------------------------------------------------------*/
-
- #if THREAD_DEBUG
-
- /* ThreadValid returns true if the 'thread' parameter is a valid thread.
- This function is primarily for use during debugging, and is called
- by the preconditions to most of the functions in Thread Library. You
- will usually not need to call this function. So that this function can
- easily be called from within other thread functions, the error code is not
- set by this function. */
- static Boolean ThreadValid(ThreadPtr thread)
- {
- if (! thread || PtrSize(thread) != sizeof(ThreadStructure)) return(false);
- if (PtrType(thread) != THREAD_TYPE_THREAD) return(false);
- if (thread->sn <= 0 || gThread.lastsn < thread->sn) return(false);
- if (gThread.main) {
- if (thread->main) {
- if (thread->appl.entry) return(false);
- }
- else {
- if (! thread->appl.entry) return(false);
- if (! thread->stack.limit) return(false);
- if (PtrType(thread->stack.limit) != THREAD_TYPE_STACK) return(false);
- }
- }
- return(true);
- }
-
- /* Validate all threads. */
- static Boolean ThreadValidAll(void)
- {
- Boolean valid;
- ThreadPtr thread;
- ThreadPtr first;
-
- valid = true;
- thread = first = gThread.queue.head;
- if (thread) {
- do {
- valid = ThreadValid(thread);
- thread = thread->next;
- } while (thread != first && valid);
- }
- return(valid);
- }
-
- #endif /* THREAD_DEBUG */
-
- /*----------------------------------------------------------------------------*/
- /* Private Queue Operations */
- /*----------------------------------------------------------------------------*/
-
- /* Threads are kept in a circular queue of threads. A doubly-linked list is
- used to make removal of an arbitrary thread (not just the head of the
- queue) efficient. */
-
- #if THREAD_DEBUG
-
- /* ThreadQueueValid returns true if the queue is valid. */
- static Boolean ThreadQueueValid(ThreadQueuePtr queue)
- {
- if (! queue) return(false);
- if (queue->nelem < 0) return(false);
- if (queue->nelem == 0 && (queue->head || queue->tail)) return(false);
- if (queue->nelem > 0 && (! queue->head || ! queue->tail)) return(false);
- if (queue->nelem == 1 && queue->head != queue->tail) return(false);
- if (queue->nelem > 1 && queue->head == queue->tail) return(false);
- if (queue->head && ! ThreadValid(queue->head)) return(false);
- if (queue->tail && ! ThreadValid(queue->tail)) return(false);
- return(true);
- }
-
- #endif /* THREAD_DEBUG */
-
- /* ThreadEnqueue adds the thread to the end of the queue. */
- static void ThreadEnqueue(ThreadQueuePtr queue, ThreadPtr thread)
- {
- require(ThreadQueueValid(queue));
- require(ThreadValid(thread));
- require(! ThreadValid(thread->next));
- require(! ThreadValid(thread->prev));
- if (! queue->head) {
- check(! queue->tail);
- queue->head = queue->tail = thread;
- thread->prev = thread->next = thread;
- }
- else {
- check(queue->tail != NULL);
- queue->tail->next = thread;
- thread->prev = queue->tail;
- thread->next = queue->head;
- queue->head->prev = thread;
- queue->tail = thread;
- }
- queue->nelem++;
- ensure(queue->tail == thread);
- ensure(ThreadValid(thread->next));
- ensure(ThreadValid(thread->prev));
- ensure(ThreadQueueValid(queue));
- }
-
- /* ThreadDequeue removes the thread from the queue. */
- static void ThreadDequeue(ThreadQueuePtr queue, ThreadPtr thread)
- {
- require(ThreadQueueValid(queue));
- require(ThreadValid(thread));
- require(ThreadValid(thread->next));
- require(ThreadValid(thread->prev));
- require(queue->nelem > 0);
- if (thread == queue->head || thread == queue->tail) {
- if (queue->nelem == 1)
- queue->head = queue->tail = NULL;
- else {
- if (thread == queue->head)
- queue->head = thread->next;
- else
- queue->tail = thread->prev;
- queue->tail->next = queue->head;
- queue->head->prev = queue->tail;
- }
- }
- else {
- check(thread->prev != thread && thread->next != thread);
- check(queue->nelem > 0);
- thread->prev->next = thread->next;
- thread->next->prev = thread->prev;
- }
- thread->next = thread->prev = NULL;
- queue->nelem--;
- ensure(! ThreadValid(thread->next));
- ensure(! ThreadValid(thread->prev));
- ensure(ThreadQueueValid(queue));
- }
-
- /*----------------------------------------------------------------------------*/
- /* Error Handling */
- /*----------------------------------------------------------------------------*/
-
- /* returns the last error that occurred, or THREAD_ERROR_NONE
- if the last routine completed successfully */
- ThreadErrorType ThreadError(void)
- {
- return(gThread.active ? gThread.active->error : gThread.error);
- }
-
- /* sets the error code that will be returned by a
- subsequent call to ThreadError */
- void ThreadErrorSet(ThreadErrorType error)
- {
- gThread.error = error;
- if (gThread.active)
- gThread.active->error = error;
- ensure(ThreadError() == error);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Thread Serial Numbers */
- /*----------------------------------------------------------------------------*/
-
- /* Every thread is assigned a unique serial number. Serial numbers are used
- to refer to threads, rather than using a pointer, since there is always
- the possiblity that a thread may have terminated before a thread pointer
- is used, which would make a thread pointer invalid. The specific
- assignment of serial numbers to threads is not defined by the interface,
- though every valid thread is guaranteed a non-zero serial number.
-
- Note: you should not assume that any thread will have a specific
- serial number. */
-
- /* returns the thread's serial number; the error code is not changed. */
- static ThreadType ThreadSN(ThreadPtr thread)
- {
- require(! thread || ThreadValid(thread));
- return(thread ? thread->sn : THREAD_NONE);
- }
-
- /* Given the serial number of a thread, ThreadFromSN returns the corresponding
- thread pointer, or NULL if there is no thread with the specified serial
- number. If the thread is found, the error code is cleared, otherwise it
- is set to THREAD_ERROR_NOT_FOUND. Since the error code is always either
- set or cleared, it is unnecessary to set the error code in many simple
- accessor functions that use ThreadFromSN. */
- static ThreadPtr ThreadFromSN(register ThreadType tsn)
- {
- register ThreadPtr thread;
- register long nthread;
-
- require(0 <= tsn && tsn <= gThread.lastsn);
- thread = gThread.queue.head;
- nthread = gThread.queue.nelem;
- while (nthread-- > 0 && thread->sn != tsn)
- thread = thread->next;
- if (thread && thread->sn != tsn)
- thread = NULL;
- ThreadErrorSet(thread ? THREAD_ERROR_NONE : THREAD_ERROR_NOT_FOUND);
- ensure(! thread || (ThreadValid(thread) && thread->sn == tsn));
- return(thread);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Accessing the Queue of Threads */
- /*----------------------------------------------------------------------------*/
-
- /* returns the number of threads in the queue */
- long ThreadCount(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(gThread.queue.nelem);
- }
-
- /* returns the main thread, or THREAD_NONE if there are no
- threads */
- ThreadType ThreadMain(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(ThreadSN(gThread.main));
- }
-
- /* returns the currently active thread, or THREAD_NONE if
- there are no threads */
- ThreadType ThreadActive(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(ThreadSN(gThread.active));
- }
-
- /* returns the first thread in the queue of threads, or
- THREAD_NONE if there are no threads */
- ThreadType ThreadFirst(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(ThreadSN(gThread.queue.head));
- }
-
- /* returns the next thread in the circular queue of threads,
- or THREAD_NONE if there are no threads */
- ThreadType ThreadNext(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? ThreadSN(thread->next) : THREAD_NONE);
- }
-
- /* returns true if the specified thread exists */
- Boolean ThreadExists(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (ThreadError() == THREAD_ERROR_NOT_FOUND)
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(thread != NULL);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Floating Point Unit */
- /*----------------------------------------------------------------------------*/
-
- /* true if there's a floating point unit */
- static Boolean ThreadHasFPU(void)
- {
- long response;
- Boolean result;
-
- result = false;
- if (Gestalt(gestaltFPUType, &response) == noErr)
- result = (response != gestaltNoFPU);
- return(result);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Application Defined Data */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadData returns the data field of the thread. The application can
- use the thread's data field for its own purposes. */
- void *ThreadData(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.data : NULL);
- }
-
- /* ThreadDataSet sets the data field of the thread. The application can
- use the thread's data field for its own purposes. */
- void ThreadDataSet(ThreadType tsn, void *data)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.data = data;
- ensure(! thread || ThreadData(tsn) == data);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Application Defined Scheduler Function */
- /*----------------------------------------------------------------------------*/
-
- /* returns the function used to schedule threads
- or NULL if the default function is being used */
- ThreadProcScheduleType ThreadProcSchedule(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(gThread.schedule);
- }
-
- /* sets the function used to schedule threads; if you
- specify NULL, then the default function will be used. */
- void ThreadProcScheduleSet(ThreadProcScheduleType schedule)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- gThread.schedule = schedule;
- ensure(ThreadProcSchedule() == schedule);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Application Defined Memory Allocation Functions */
- /*----------------------------------------------------------------------------*/
-
- /* returns the function used to allocate memory
- or NULL if the default function is being used */
- ThreadProcAllocateType ThreadProcAllocate(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(gThread.allocate);
- }
-
- /* sets the function used to allocate memory; if you
- specify NULL, then the default function will be used. */
- void ThreadProcAllocateSet(ThreadProcAllocateType allocate)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- gThread.allocate = allocate;
- ensure(ThreadProcAllocate() == allocate);
- }
-
- /* returns the function used to dispose of memory,
- or NULL if the default function is being used */
- ThreadProcDisposeType ThreadProcDispose(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(gThread.dispose);
- }
-
- /* sets the function used to dispose of memory; if you
- specify NULL, then the default function will be used. */
- void ThreadProcDisposeSet(ThreadProcDisposeType dispose)
- {
- gThread.dispose = dispose;
- ThreadErrorSet(THREAD_ERROR_NONE);
- ensure(ThreadProcDispose() == dispose);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Application Defined Protocol Functions */
- /*----------------------------------------------------------------------------*/
-
- /* returns the begin function for the thread */
- ThreadProcBeginEndType ThreadProcBegin(ThreadType tsn)
- {
- ThreadPtr thread;
-
- require(tsn != ThreadMain());
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.begin : NULL);
- }
-
- /* sets the begin function for the thread */
- void ThreadProcBeginSet(ThreadType tsn, ThreadProcBeginEndType begin)
- {
- ThreadPtr thread;
-
- require(tsn != ThreadMain());
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.begin = begin;
- ensure(! thread || ThreadProcBegin(tsn) == begin);
- }
-
- /* returns the end function for the thread */
- ThreadProcBeginEndType ThreadProcEnd(ThreadType tsn)
- {
- ThreadPtr thread;
-
- require(tsn != ThreadMain());
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.end : NULL);
- }
-
- /* sets the end function for the thread */
- void ThreadProcEndSet(ThreadType tsn, ThreadProcBeginEndType end)
- {
- ThreadPtr thread;
-
- require(tsn != ThreadMain());
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.end = end;
- ensure(! thread || ThreadProcEnd(tsn) == end);
- }
-
- /* returns the resume function for the thread */
- ThreadProcType ThreadProcResume(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.resume : NULL);
- }
-
- /* sets the resume function for the thread */
- void ThreadProcResumeSet(ThreadType tsn, ThreadProcType resume)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.resume = resume;
- ensure(! thread || ThreadProcResume(tsn) == resume);
- }
-
- /* returns the suspend function for the thread */
- ThreadProcType ThreadProcSuspend(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.suspend : NULL);
- }
-
- /* sets the suspend function for the thread */
- void ThreadProcSuspendSet(ThreadType tsn, ThreadProcType suspend)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.suspend = suspend;
- ensure(! thread || ThreadProcSuspend(tsn) == suspend);
- }
-
- /* returns the entry function for the thread */
- ThreadProcType ThreadProcEntry(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->appl.entry : NULL);
- }
-
- /* sets the entry function for the thread */
- void ThreadProcEntrySet(ThreadType tsn, ThreadProcType entry)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->appl.entry = entry;
- ensure(! thread || ThreadProcEntry(tsn) == entry);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Information About the Stack */
- /*----------------------------------------------------------------------------*/
-
- /* returns the recommended minimum stack size for a thread */
- ThreadSizeType ThreadStackMinimum(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(LMGetMinStack());
- }
-
- /* returns the default stack size for a thread */
- ThreadSizeType ThreadStackDefault(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(LMGetDefltStack());
- }
-
- /* returns the total size of the thread's stack */
- ThreadSizeType ThreadStackSize(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->stack.size : 0);
- }
-
- /* returns the amount of space remaining in the thread's stack */
- ThreadSizeType ThreadStackSpace(ThreadType tsn)
- {
- ThreadSizeType result;
- ThreadStackPtr sp;
- ThreadPtr thread;
-
- result = 0;
- thread = ThreadFromSN(tsn);
- if (thread) {
- sp = ThreadStackPointer(thread);
- check(sp >= thread->stack.limit);
- result = sp - thread->stack.limit;
- }
- ensure(result >= 0);
- return(result);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Scheduling */
- /*----------------------------------------------------------------------------*/
-
- /* The three functions ThreadSchedule, ThreadActivate, and ThreadYield
- handle the scheduling and context switching of threads. These functions
- will be executed the most often of any of the functions in this file, and
- therefore will have the greatest impact on the efficiency of Thread
- Library. If you find Thread Library's context switches too slow, you
- could improve the efficiency of these functions. */
-
- /* Returns true if the thread is enabled. An enabled thread is eligible for
- scheduling, while a disabled thread is not scheduled. */
- Boolean ThreadEnabled(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->enabled : false);
- }
-
- /* enables or disables the thread */
- void ThreadEnabledSet(ThreadType tsn, Boolean enabled)
- {
- ThreadPtr thread;
-
- require(tsn != ThreadMain());
- require(enabled == true || enabled == false);
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->enabled = enabled;
- ensure(thread ? ThreadEnabled(tsn) == enabled :
- ThreadEnabled(tsn) == false);
- }
-
- /* returns the time at which the thread will become active */
- ThreadTicksType ThreadWakeTime(ThreadType tsn)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- return(thread ? thread->wake : 0);
- }
-
- /* sets the time at which the thread will become active */
- void ThreadWakeTimeSet(ThreadType tsn, ThreadTicksType wake)
- {
- ThreadPtr thread;
-
- require(0 <= wake && wake <= THREAD_TICKS_MAX);
- thread = ThreadFromSN(tsn);
- if (thread)
- thread->wake = wake;
- ensure(ThreadWakeTime(tsn) == wake);
- }
-
- /* this is identical to ThreadSleepIntervalSet, but for greater efficiency it
- takes a pointer to a thread. */
- static void ThreadSleepIntervalSetPtr(ThreadPtr thread, ThreadTicksType sleep, ThreadTicksType ticks)
- {
- require(ThreadValid(thread));
- require(0 <= sleep && sleep <= THREAD_TICKS_MAX);
- /* Set the thread's wakeup time, being careful with overflow since the
- sleep parameter could be THREAD_TICKS_MAX. */
- if (sleep > THREAD_TICKS_MAX - ticks)
- thread->wake = THREAD_TICKS_MAX;
- else
- thread->wake = ticks + sleep;
- }
-
- /* sets the amount of time that the specified thread will remain inactive */
- void ThreadSleepIntervalSet(ThreadType tsn, ThreadTicksType sleep)
- {
- ThreadPtr thread;
-
- thread = ThreadFromSN(tsn);
- if (thread)
- ThreadSleepIntervalSetPtr(thread, sleep, LMGetTicks());
- }
-
- /* ThreadSleepSet is the same as ThreadSleepIntervalSet, but is included
- for compatability with prior versions of Thread Library. */
- void ThreadSleepSet(ThreadType tsn, ThreadTicksType sleep)
- {
- ThreadSleepIntervalSet(tsn, sleep);
- }
-
- /* EventPending returns true if an event is pending. Most events are posted
- to the event queue, so we can determine if an event is pending simply
- by examining the head of the event queue. Update events are not posted
- to the event queue, so we need to call CheckUpdate to detect them. Since
- CheckUpdate needs to traverse the window list and could therefore be
- relatively slow, we make the interval between calls to CheckUpdate as
- large as possible without severely degrading response time. Once every
- 1/4 second seems like a good interval to call CheckUpdate.
-
- Activate and deactivate events are also not posted to the event queue.
- Instead, the system just sets the low-memory globals CurActivate and
- CurDeactive. However, these globals are not cleared once the event is
- handled. Therefore, I haven't figured out a reliable method to test
- for activate and deactivate events, so those events are just ignored.
-
- Checking for events using this method, rather than using EventAvail, is
- preferrable for two reasons. First, and most importantly, EventAvail
- can let the application be switched out, which could result in some
- confusing information being displayed about the application's memory
- partition in the Finder's About window and possibly other
- incompatabilities. Second, EventAvail is a fairly slow trap, and we
- want context switches to be as fast as possible. */
- static Boolean EventPending(ThreadTicksType ticks)
- {
- #define UPDATE_PENDING_INTERVAL (15)
- #define EVENT_PENDING_INTERVAL (1)
- static ThreadTicksType nextUpdate;
- static ThreadTicksType nextEvent;
- EventRecord event;
- Boolean pending;
-
- pending = false;
- if (ticks >= nextEvent) {
- pending = (LMGetEventQueue()->qHead != NULL);
- nextEvent = ticks + EVENT_PENDING_INTERVAL;
- if (! pending && ticks >= nextUpdate) {
- pending = CheckUpdate(&event);
- nextUpdate = ticks + UPDATE_PENDING_INTERVAL;
- }
- }
- return(pending);
- }
-
- /* ThreadSchedulePtr is identical to ThreadSchedule, except it returns a
- pointer to a thread instead of a thread serial number. This makes context
- switches triggered via ThreadYield more efficient, since we already have
- direct access to the relevant thread pointers and so do not need to waste
- time converting to and from thread serial numbers. */
- static ThreadPtr ThreadSchedulePtr(register ThreadTicksType ticks)
- {
- register ThreadPtr active; /* active thread */
- register ThreadPtr newthread; /* thread to switch to */
- ThreadType suggestedSN; /* serial number of thread suggested by application */
- ThreadPtr suggestedPtr; /* pointer to thread suggested by application */
-
- require(ThreadValid(gThread.active));
- if (EventPending(ticks)) {
- /* an event is pending, so return main thread */
- newthread = gThread.main;
- }
- else {
- /* round-robbin search for a thread that needs to be woken up */
- active = gThread.active;
- newthread = active->next;
- check(ThreadValid(newthread));
- while (newthread != active &&
- (newthread->wake > ticks ||
- ! newthread->enabled))
- {
- newthread = newthread->next;
- check(ThreadValid(newthread));
- }
- if (newthread == active &&
- (newthread->wake > ticks ||
- ! newthread->enabled))
- {
- /* no thread needs to be woken up, so return main thread */
- newthread = gThread.main;
- }
- }
- if (gThread.schedule) {
- /* use application-defined callback */
- suggestedSN = gThread.schedule(newthread->sn);
- if (suggestedSN != THREAD_NONE && suggestedSN != newthread->sn) {
- suggestedPtr = ThreadFromSN(suggestedSN);
- if (suggestedPtr && suggestedPtr->enabled)
- newthread = suggestedPtr;
- }
- }
- ensure(ThreadValid(newthread));
- ensure(newthread->enabled);
- return(newthread);
- }
-
- /* returns the next thread to activate */
- ThreadType ThreadSchedule(void)
- {
- ThreadErrorSet(THREAD_ERROR_NONE);
- return(ThreadSN(ThreadSchedulePtr(LMGetTicks())));
- }
-
- /* ThreadResume restores the context of the active thread. It is called
- before a thread resumes execution, but after the thread's stack has
- been restored. */
- static void ThreadResume(register ThreadPtr thread)
- {
- register ThreadQueuePtr queue; /* for faster access to queue */
-
- require(thread == gThread.active && ThreadValid(thread));
-
- /* for greater efficiency, put things into registers */
- queue = &gThread.queue;
-
- /* Set the low-memory globals for the thread. We bypass any
- traps or glue (like SetApplLimit) to keep the OS from
- preventing us from changing these globals and to keep
- context switches fast. */
- LMSetHeapEnd(thread->lm.heapEnd);
- LMSetApplLimit(thread->lm.applLimit);
- LMSetHiHeapMark(thread->lm.hiHeapMark);
-
- /* Move the thread to the tail of the queue so that it is rescheduled
- to run after all other threads (though scheduling also depends on
- wake times and priority). Functionally, the move-to-tail is always
- equivalent to a dequeue followed by an enqueue, but it's optimized
- for the most common case where the thread is already at the front
- of the queue. Since the queue is circular, we can just advance the
- head and tail pointers to achieve the same result. We do the
- optimization in-line since the function call overhead could be
- significant over many context switches. */
- if (thread == queue->head) {
- queue->head = thread->next;
- queue->tail = thread;
- }
- else if (thread != queue->tail) {
- ThreadDequeue(queue, thread);
- ThreadEnqueue(queue, thread);
- }
-
- /* call the application's resume function -- this must be called prior to
- disposing of a zombie thread so that the application can switch its
- context to the new thread before destroying the data in the old thread */
- if (thread->appl.resume)
- thread->appl.resume(thread->appl.data);
-
- /* dispose of the memory allocated for a previously terminated
- thread (see ThreadEndPtr) */
- if (gThread.zombie) {
- ThreadDispose(gThread.zombie);
- gThread.zombie = NULL;
- }
- }
-
- /* ThreadSuspend saves the context of the active thread. It is called
- when a thread is deactivated, but before the next thread's stack
- or environment have been restored. */
- static void ThreadSuspend(register ThreadPtr thread)
- {
- require(thread == gThread.active && ThreadValid(thread));
-
- /* save low-memory globals */
- thread->lm.heapEnd = LMGetHeapEnd();
- thread->lm.applLimit = LMGetApplLimit();
- thread->lm.hiHeapMark = LMGetHiHeapMark();
-
- /* call the application's suspend function */
- if (thread->appl.suspend)
- thread->appl.suspend(thread->appl.data);
- }
-
- /* ThreadActivatePtr is identical to ThreadActivate, except it takes a pointer
- to a thread instead of a thread serial number. This makes context switches
- triggered via ThreadYield more efficient, since we already have direct
- access to the relevant thread pointers and so don't need to waste time
- converting to and from thread serial numbers.
-
- The context switch is accomplished by saving the CPU context with
- ThreadRegistersSave and then calling ThreadRegistersRestore, which
- jumps to the environment saved with ThreadRegistersSave when the
- thread being activated was last suspended. We don't have to use any
- assembly language glue since ThreadRegistersSave saved the value of
- the stack pointer, which at the time of the call to ThreadRegistersSave
- pointed somewhere in the thread's stack. The ThreadRegistersRestore
- function will restore the value of the stack pointer and will jump
- to the statement from which to resume the thread. ThreadRegistersRestore
- also restores all other registers. */
- static void ThreadActivatePtr(register ThreadPtr oldthread, register ThreadPtr newthread)
- {
- require(! oldthread || (oldthread == gThread.active && ThreadValid(gThread.active)));
- require(ThreadValid(newthread));
- if (oldthread != newthread) {
-
- /* save thread's registers */
- if (oldthread && oldthread->fpu)
- ThreadRegistersFPSave(oldthread->registers.fp);
- if (oldthread && ThreadRegistersGPSave(oldthread->registers.gp)) {
-
- /* The thread is being activated, so restore the thread's context.
- We need to get the thread from the global variable gThread.active,
- since local variables are not yet defined. */
- ThreadResume(gThread.active);
- }
- else {
-
- /* suspend the current thread */
- if (oldthread)
- ThreadSuspend(oldthread);
-
- /* Jump to the specified thread. This suspends the current thread
- and returns at the ThreadRegistersGPSave statement above (unless this
- is the first time the thread is being executed, in which case it
- returns at the ThreadRegistersGPSave statement in ThreadBegin). The
- contents of the stack will be correct as soon as
- ThreadRegistersGPRestore has completed, but ThreadResume must be called
- before the thread can start executing again. */
- gThread.active = newthread;
- if (newthread->fpu)
- ThreadRegistersFPRestore(newthread->registers.fp);
- ThreadRegistersGPRestore(newthread->registers.gp);
- check(false); /* doesn't return */
- }
- }
- /* can't evaluate this postcondition--no local variables */
- /* ensure(gThread.active == thread); */
- }
-
- /* deactivates the currently active thread and makes the specified thread the
- active thread */
- void ThreadActivate(ThreadType tsn)
- {
- ThreadPtr thread;
-
- require(ThreadValid(gThread.active));
- thread = ThreadFromSN(tsn);
- if (thread)
- ThreadActivatePtr(gThread.active, thread);
- /* ensure(! thread || ThreadActive() == tsn); */ /* can't evaluate this postcondition */
- }
-
- /* activates the next scheduled thread */
- void ThreadYield(ThreadTicksType sleep)
- {
- ThreadTicksType ticks;
-
- require(ThreadValid(gThread.active));
- /* We set the active thread's wakeup time before we run the scheduler and
- before the active thread is suspended. We need to set the wakeup time
- so that the thread scheduler will work correctly. Also, the thread
- scheduler could take several ticks to complete, while we want to
- calculate the wakeup time to be as close as possible to the wakeup
- time specified by the sleep parameter */
- ticks = LMGetTicks();
- ThreadErrorSet(THREAD_ERROR_NONE);
- ThreadSleepIntervalSetPtr(gThread.active, sleep, ticks);
- ThreadActivatePtr(gThread.active, ThreadSchedulePtr(ticks));
- }
-
- /* returns the maximum time till the next call to ThreadYield */
- ThreadTicksType ThreadYieldInterval(void)
- {
- ThreadPtr thread; /* for iterating through queue of threads */
- ThreadPtr active; /* currently active thread */
- ThreadTicksType ticks; /* current tick count */
- ThreadTicksType interval; /* interval till next call to ThreadYield */
-
- require(ThreadValid(gThread.active));
- ThreadErrorSet(THREAD_ERROR_NONE);
- ticks = LMGetTicks();
- active = gThread.active;
- thread = active->next;
- interval = THREAD_TICKS_MAX;
- check(ThreadValid(thread));
- while (thread != active && interval) {
- if (thread->enabled) {
- if (thread->wake <= ticks)
- interval = 0;
- else if (thread->wake - ticks < interval)
- interval = thread->wake - ticks;
- }
- thread = thread->next;
- check(ThreadValid(thread));
- }
- ensure(0 <= interval && interval <= THREAD_TICKS_MAX);
- return(interval);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Thread Creation and Destruction */
- /*----------------------------------------------------------------------------*/
-
- /*----------------------------------------------------------------------------*/
- /* Destroying Threads */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadDispose disposes of the memory allocated for the thread. The thread
- must not be active when this is called. ThreadDispose can be called either
- to dispose of a partially created thread (whose creation failed), or to
- dispose of a thread that has terminated. */
- static void ThreadDispose(ThreadPtr thread)
- {
- require(! thread || thread != gThread.active);
- if (thread) {
- if (thread->stack.limit && ! thread->main)
- PtrEnd(thread->stack.limit, thread->stack.size, THREAD_TYPE_STACK);
- PtrEnd(thread, sizeof(ThreadStructure), THREAD_TYPE_THREAD);
- }
- }
-
- /* ThreadEndPtr is identical in function to ThreadEnd (see below), but it
- takes a pointer to a thread rather than a thread serial number. */
- static void ThreadEndPtr(ThreadPtr thread)
- {
- require(ThreadValid(thread));
- if (thread) {
-
- /* suspend the thread if it's the active thread */
- if (thread == gThread.active)
- ThreadSuspend(thread);
-
- /* call the application's end function */
- if (thread->appl.end)
- thread->appl.end(thread->sn, thread->appl.data);
-
- /* remove the thread from the queue so that it is no longer
- accessible and will not be scheduled for execution */
- ThreadDequeue(&gThread.queue, thread);
-
- if (thread == gThread.main) {
-
- /* We're disposing of the main thread, so this is a good time
- to do any final cleanup of Thread Library. */
-
- /* clear globals */
- check(gThread.active == thread);
- check(gThread.queue.nelem == 0);
- gThread.main = gThread.active = NULL;
- }
- else if (thread == gThread.active) {
-
- /* We're disposing of the active thread, but we can't dispose of
- the thread's stack since we're still using that stack. So
- we delay disposal of the thread until the next thread is
- activated; the thread will be disposed of in ThreadResume,
- which is executed when the next scheduled thread is activated. */
- check(! gThread.zombie);
- gThread.zombie = thread;
-
- /* activate the main thread */
- ThreadActivatePtr(NULL, gThread.main);
- check(false); /* doesn't return */
- }
- else {
- /* dispose of the memory allocated for the thread */
- ThreadDispose(thread);
- }
- }
- }
-
- /* ThreadEnd removes the thread from the queue and disposes of the memory
- allocated for the thread. If the thread is the active thread then the
- next scheduled thread is activated. All threads (other than the main
- thread) must be disposed of before the main thread can be disposed of. */
- void ThreadEnd(ThreadType tsn)
- {
- ThreadPtr thread;
-
- require(tsn == THREAD_NONE ||
- (ThreadCount() > 1 ? tsn != ThreadMain() : tsn == ThreadMain()));
- if (tsn != THREAD_NONE) {
- thread = ThreadFromSN(tsn);
- if (thread)
- ThreadEndPtr(thread);
- }
- /* ensure(! ThreadValid(ThreadFromSN(tsn))); */ /* executing this would set the error code */
- }
-
- /* ThreadEndAnyNext returns the next thread to be disposed of. Any thread
- other than the main thread may be returned. */
- static ThreadType ThreadEndAnyNext(void)
- {
- ThreadType tsn;
-
- tsn = THREAD_NONE;
- if (ThreadCount() > 1) {
- tsn = ThreadFirst();
- if (tsn == ThreadMain()) {
- tsn = ThreadNext(tsn);
- check(tsn != ThreadFirst());
- }
- check(tsn != ThreadMain());
- }
- ensure( ( ThreadCount() <= 1 && tsn == THREAD_NONE) ||
- ( ThreadCount() > 1 &&
- ThreadValid(ThreadFromSN(tsn)) &&
- tsn != ThreadMain()));
- return(tsn);
- }
-
- /* ThreadEndAny disposes of any one thread other than the main thread. */
- static void ThreadEndAny(void)
- {
- ThreadEnd(ThreadEndAnyNext());
- /* ensure((old ThreadCount()) == 0 || ThreadCount() == (old ThreadCount()) - 1); */
- }
-
- /* ThreadEndAllExceptMain disposes of all threads other than the main thread. */
- static void ThreadEndAllExceptMain(void)
- {
- while (ThreadCount() > 1) {
- ThreadEndAny();
- /* check(ThreadCount() == (old ThreadCount()) - 1); */
- }
- ensure(ThreadCount() <= 1);
- }
-
- /* ThreadEndAll disposes of all threads, including the main thread.
- ThreadEndAll is useful when your application is terminating and
- you want to dispose of any threads that may still exist.
- ThreadEndAll can be called only from within the main thread. */
- void ThreadEndAll(void)
- {
- require(ThreadActive() == ThreadMain());
- ThreadEndAllExceptMain();
- ThreadEnd(ThreadMain());
- ensure(ThreadCount() == 0);
- }
-
- /*----------------------------------------------------------------------------*/
- /* Creating Threads */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadBeginMain creates the main application thread and returns the main
- thread's serial number. */
- ThreadType ThreadBeginMain(ThreadProcType suspend, ThreadProcType resume,
- void *data)
- {
- ThreadPtr thread; /* the new thread */
- Boolean success; /* true if thread was created successfully */
-
- require(! gThread.main);
-
- /* This is always the first routine called for Thread Library,
- so this is a good place to do any one-time initializations
- of the library. Conversely, when the main thread is disposed
- of is a good time to do any final cleanup of the library. */
-
- thread = NULL;
- success = false;
- ThreadErrorSet(THREAD_ERROR_NONE);
-
- /* allocate thread structure */
- thread = PtrBeginClear(sizeof(ThreadStructure), THREAD_TYPE_THREAD);
- if (! thread && ! ThreadError())
- ThreadErrorSet(memFullErr);
- if (thread) {
-
- /* initialize thread structure */
- thread->fpu = ThreadHasFPU();
- thread->enabled = true;
- thread->main = true;
- thread->appl.suspend = suspend;
- thread->appl.resume = resume;
- thread->appl.data = data;
- thread->sn = ++gThread.lastsn;
- thread->stack.limit = LMGetApplLimit();
- thread->stack.top = LMGetCurStackBase();
- thread->stack.size = thread->stack.top - thread->stack.limit;
-
- /* save values of low-memory globals */
- thread->lm.heapEnd = LMGetHeapEnd();
- thread->lm.applLimit = LMGetApplLimit();
- thread->lm.hiHeapMark = LMGetHiHeapMark();
-
- /* now that the thread is ready to use, append it to the queue of
- threads so that it can be scheduled for execution */
- ThreadEnqueue(&gThread.queue, thread);
-
- /* make this thread the active and main thread */
- gThread.active = thread;
- gThread.main = thread;
-
- /* we've successfully created the main thread */
- success = true;
- }
-
- /* cleanup if failed */
- if (! success && thread) {
- ThreadDispose(thread);
- thread = NULL;
- }
-
- ensure(thread ? ThreadValid(thread) && ! ThreadError() : ThreadError());
- ensure(thread == gThread.main);
- ensure(gThread.active == gThread.main);
- return(ThreadSN(thread));
- }
-
- /* called to start executing a new thread; must be a separate
- function to work on powerpc */
- static void ThreadStart(void)
- {
- /* call the thread's begin function */
- if (gThread.active->appl.begin)
- gThread.active->appl.begin(gThread.active->sn, gThread.active->appl.data);
-
- /* set up the thread's context */
- ThreadResume(gThread.active);
-
- /* call the thread's entry point */
- gThread.active->appl.entry(gThread.active->appl.data);
-
- /* dispose of the thread and switch to the next scheduled thread */
- ThreadEndPtr(gThread.active);
-
- ensure(false); /* never returns */
- }
-
- /* ThreadBegin creates a new thread and returns the thread's serial number.
- You must create the main thread with ThreadBeginMain before you can call
- ThreadBegin. */
- ThreadType ThreadBegin(ThreadProcType entry,
- ThreadProcType suspend,
- ThreadProcType resume,
- void *data, ThreadSizeType stack_size)
- {
- ThreadPtr thread; /* new thread */
- Boolean success; /* true if thread was created successfully */
-
- require(ThreadValid(gThread.main));
- require(entry != NULL);
- require(0 <= stack_size);
-
- /* clear results */
- thread = NULL;
- success = false;
- ThreadErrorSet(THREAD_ERROR_NONE);
-
- /* allocate thread structure */
- thread = PtrBeginClear(sizeof(ThreadStructure), THREAD_TYPE_THREAD);
- if (thread) {
-
- /* The main thread uses the application's regular stack, while
- nonrelocatable blocks are allocated to contain the stacks of
- all other threads. The thread's stack size is aligned to
- a double-word size (8 byte boundary) for compatability
- with the PowerPC architecture. */
- if (! stack_size)
- stack_size = ThreadStackDefault();
- stack_size += stack_size % 8;
- thread->stack.limit = PtrBegin(stack_size, THREAD_TYPE_STACK);
- if (thread->stack.limit) {
-
- /* initialize thread structure */
- thread->fpu = ThreadHasFPU();
- thread->enabled = true;
- thread->sn = ++gThread.lastsn;
- thread->appl.entry = entry;
- thread->appl.suspend = suspend;
- thread->appl.resume = resume;
- thread->appl.data = data;
- thread->stack.size = stack_size;
- thread->stack.top = thread->stack.limit + stack_size;
-
- /* Since all threads other than the main thread use stacks
- allocated in the application's heap, we need to disable the
- stack sniffer VBL task by setting the low-memory global
- variable StkLowPt to 0. Otherwise, the stack sniffer would
- generate system error #28. */
- LMSetStkLowPt(NULL);
-
- /* Certain low-memory globals divide the stack and heap.
- We change these globals when a thread other than the
- main thread is activated so that certain OS traps will
- work correctly. */
- thread->lm.heapEnd = thread->stack.limit;
- thread->lm.applLimit = thread->stack.limit;
- thread->lm.hiHeapMark = thread->stack.limit;
-
- /* Set up the new thread's registers so that we'll jump
- here when the thread is first activated. */
- if (thread->fpu)
- ThreadRegistersFPSave(thread->registers.fp);
- if (ThreadRegistersGPSave(thread->registers.gp)) {
-
- /* We're now executing the *new* thread (I know, it doesn't
- look like it, but it's all due to the magic [hell?] of
- non-local gotos and global variables). At this point,
- the stack is empty, so we can't access any local variables.
- All subsequent executions of the thread will go through the
- ThreadRegistersSave call in ThreadActivate. */
- ThreadStart();
- check(false); /* never returns */
- }
-
- /* Threads other than the main thread have their own private stack.
- To be able to switch stacks, the first time the registers are saved
- we need to set the stack pointer register that we saved in the
- general purpose register array to the top of the thread's private
- stack. We do this by knowing the index of the stack pointer in the
- array of general purpose registers. For the M68K, we also need to
- set the frame pointer to NULL. For the powerpc, we need to leave
- space from the top of the stack for the linkage area. */
- #ifdef powerc
- thread->registers.gp[REGISTER_SP] = (long) (thread->stack.top - 12);
- #else /* powerc */
- thread->registers.gp[REGISTER_SP] = (long) thread->stack.top;
- thread->registers.gp[REGISTER_FP] = 0;
- #endif /* powerc */
-
- /* now that the thread is ready to use, append it to the queue of
- threads so that it can be scheduled for execution */
- ThreadEnqueue(&gThread.queue, thread);
-
- /* We've now successfully created a new thread and set things up so
- that the first time the thread is invoked we'll call the thread's
- entry point. We let the application call ThreadYield in its own
- time to switch contexts. In other words, the new thread doesn't
- start executing until it is scheduled to start or it is
- specifically activated. */
- success = true;
- }
- }
-
- /* cleanup if failed */
- if (! success && thread) {
- ThreadDispose(thread);
- thread = NULL;
- }
-
- ensure(thread ? ThreadValid(thread) && ! ThreadError() : ThreadError());
- return(ThreadSN(thread));
- }
-